import { Box3, Color, Frustum, Mesh, MeshBasicMaterial, Object3D, PerspectiveCamera, Sphere, SphereGeometry, Vector3 } from "three";
import Config from "../Config";
import GeoUtils from "../utils/GeoUtils";
import CameraUtil from "../utils/CameraUtil";
import GlobalScene from "../render/GlobalScene";
import ImageTileMesh from "../mesh/ImageTileMesh";
import MeshUtil from "../utils/MeshUtil";

export default class MapController {
    private camera: PerspectiveCamera;
    private lastTileNumStrSet: Set<string> = new Set < string > ();
    private tileMap: Map<string, ImageTileMesh> = new Map < string, ImageTileMesh> ();
    private baseTileUrl = 'https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/';

    public constructor(camera: PerspectiveCamera) {
    this.camera = camera;
}

    public computeLevel(sightPoint: Vector3) {
    // let sightPoint = this.getSightPoint(this.camera);
    let distanceCameraToSight = this.camera.position.distanceTo(sightPoint);
    // let ellipsoidCenter = Config.EllipsoidConfig.center;
    // let distanceCameraToCenter = this.camera.position.distanceTo(ellipsoidCenter);
    // let currentRadius = sightPoint.clone().distanceTo(ellipsoidCenter);
    // let mapLevel = Math.log2(distanceCameraToCenter / distanceCameraToSight);
    let mapLevel = 23 - Math.log2(distanceCameraToSight);
    mapLevel = Math.floor(mapLevel) + 3;
    if (mapLevel < 3) {
        mapLevel = 3;
    }
    // console.log('mapLevel: ' + mapLevel + ', ' + distanceCameraToSight);

    return mapLevel;
}

    public computeVisibleTiles(sightPoint: Vector3, mapLevel: number) {
    //思路：1.先把ecef坐标系的sightPoint转换到wgs84，然后再转换到web墨卡托坐标系，根据web墨卡托坐标系的值计算出瓦片的编号。使用当前的编号逐步的向周边扩散
    //2. 扩散时的每一个瓦片的中心点和4个角落的点从web墨卡托坐标系转换到wgs84,然后再转换到ecef，计算出ecef的中心点和ecef的四个角落点的最大距离，然后以ecef的中心点为球心，最大距离为半径，构建出一个球体
    //3. 扩散时计算每个瓦片的球体是否在相机的视椎体内部，如果在视椎体内部，就从该瓦片继续向周边扩散；如果不在视椎体内部，就停止扩散。另外要判断出该瓦片是否被地球遮挡了，可以从地球的球心向瓦片的中心点发射出一条射线，计算出这条射线和瓦片球体的相交点，用最远的相交点向相机的位置发射一条射线，计算出该射线是否和地球有相交点，如果没有，就说明该瓦片没有被地球遮挡，反之，就说明该瓦片被球体遮挡了
    //4. 从一个瓦片向周边扩散时要注意避免瓦片的重复，因为很有可能周边的部分瓦片可能之前已经被扩散访问过了。这里可以使用Set集合存储瓦片的行列号，避免重复访问
    //5. 判断瓦片是否被地球遮挡可以考虑使用更高效的算法：地球球心到相机位置的向量A，地球球心到瓦片包围球中心点的向量B，求出A和B之间的夹角，如果夹角大于90度就是被遮挡，反之，没有被遮挡
    //6. 上面第5步还可以进一步优化：计算出瓦片包围球到相机位置最近的点a，从地球球心到点a构造一条向量A，从地球球心到相机位置构造一条向量B，求出A和B之间的夹角，如果夹角大于90度就是被遮挡，反之，没有被遮挡

    let webPoint = GeoUtils.ecefToWeb(sightPoint); //从ecef坐标系转换到web墨卡托坐标系
    let column = GeoUtils.getC10(webPoint.x, mapLevel); //瓦片的列号
    let row = GeoUtils.getR10(webPoint.y, mapLevel); //瓦片的行号

    CameraUtil.updateCameraMatrix(this.camera); //计算瓦片可视性前，需要先更新相机矩阵

    let visibleTileStrSet: Set<string> = new Set < string > ();
    console.log('column:' + column + ', row:' + row + ', mapLevel:' + mapLevel);
    this.loadAroundTilesNum(column, row, mapLevel, visibleTileStrSet);
    return visibleTileStrSet;
}
    /**层级切换时,加载瓦片 */
    public loadZoomTiles(visibleTileStrSet: Set < string >, zoom: 'down' | 'up', mapLevel) {
    //思路：当地图层级切换时该方法会出现地图闪动的问题，地图层级切换时瓦片调度策略的思路如下：
    //1. 计算出层级变化后的新瓦片编号，然后对旧瓦片做标注：如果是层级放大，旧瓦片是新瓦片的父节点，旧瓦片的标注最大为4；如果是层级缩小，旧瓦片是新瓦片的子节点，旧瓦片的标注为1；如果没有被标注到的旧瓦片就是可以直接删除的
    //2. 层级放大/缩小时，每次新瓦片的资源加载成功，就把旧瓦片的标注减去1,当旧瓦片的标注为0的时候，就删除旧瓦片
    //3. 虽然上面提到了瓦片的父子关系，但是程序实现的时候不要用树形结构，每个新瓦片计算出对应的父节点或子节点的瓦片编号，然后从this.tileMap中获取旧瓦片对象，给旧瓦片对象设置标注属性
    //4. 可以用2*CurrentMapLevel,和2*CurrentMapLevel+1得出子节点的瓦片编号；Math.floor(CurrentMapLevel/2)得出父节点的瓦片编号
    if (zoom == 'down') {
        this.loadZoomDown(visibleTileStrSet, mapLevel);
    } else if (zoom == 'up') {
        this.loadZoomUp(visibleTileStrSet, mapLevel);
    }

    this.lastTileNumStrSet = visibleTileStrSet;
}

    private loadZoomDown(visibleTileStrSet, mapLevel){
    console.log('zoomDown');
    let newTileArray: Array<{ tileIID: string, tileMesh: Object3D }> = new Array < { tileIID: string, tileMesh: Object3D } > ();
    visibleTileStrSet.forEach(tileNumStr => {
        if (this.tileMap.has(tileNumStr)) {
            return;
        }
        let tileNumJson: { row: number, column: number, mapLevel: number } = JSON.parse(tileNumStr);
        let tileX = tileNumJson.column;
        let tileY = tileNumJson.row;
        let tileZ = tileNumJson.mapLevel;

        let parentTileX = Math.floor(tileX / 2);
        let parentTileY = Math.floor(tileY / 2);
        let parentTileZ = tileZ - 1;
        let parentTileNumJson = { row: parentTileY, column: parentTileX, mapLevel: parentTileZ };
        let parentTileNumStr = JSON.stringify(parentTileNumJson);
        let oldTileMesh = this.tileMap.get(parentTileNumStr);

        (oldTileMesh) ? oldTileMesh.relationCount++ : null;
        let self = this;
        let param = {
            tileNum: { x: tileX, y: tileY, z: tileZ }, urlBase: this.baseTileUrl, callback: function (curTileMesh) {
                (oldTileMesh) ? oldTileMesh.relationCount-- : null;
                (oldTileMesh) ? (oldTileMesh.material['depthTest'] = false) : null;

                if (oldTileMesh && oldTileMesh.relationCount <= 0) { //当旧瓦片的标记被清空时，删除该瓦片
                    oldTileMesh.dispose();
                    self.tileMap.delete(parentTileNumStr);
                }

                curTileMesh.visible = true;
            }
        }
        let tileMesh = new ImageTileMesh(param);
        newTileArray.push({ tileIID: tileNumStr, tileMesh: tileMesh });
    });

    this.removeTileWithZoom(mapLevel); //删除没有被标记到的瓦片
    newTileArray.forEach(element => {
        this.tileMap.set(element.tileIID, element.tileMesh);
        GlobalScene.instance.addTile(element.tileMesh);
    });
}

    private loadZoomUp(visibleTileStrSet, mapLevel){
    console.log('zoomUp');
    let newTileArray: Array<{ tileIID: string, tileMesh: Object3D }> = new Array < { tileIID: string, tileMesh: Object3D } > ();;
    visibleTileStrSet.forEach(tileNumStr => {
        if (this.tileMap.has(tileNumStr)) {
            return;
        }
        let tileNumJson: { row: number, column: number, mapLevel: number } = JSON.parse(tileNumStr);
        let tileX = tileNumJson.column;
        let tileY = tileNumJson.row;
        let tileZ = tileNumJson.mapLevel;

        let sonTileZ = tileZ + 1;
        let sonTileArray: Array<{ tileIID: string, tileMesh: Object3D }> = new Array < { tileIID: string, tileMesh: Object3D } > ();
        for (let i = 0; i < 2; i++) {
            for (let j = 0; j < 2; j++) {
                let sonTileX = tileX * 2 + i;
                let sonTileY = tileY * 2 + j;

                let sonTileJson = { row: sonTileY, column: sonTileX, mapLevel: sonTileZ };
                let sonTileJsonStr = JSON.stringify(sonTileJson);
                let sonTileMesh = this.tileMap.get(sonTileJsonStr);
                if (sonTileMesh) {
                    sonTileArray.push({ tileIID: sonTileJsonStr, tileMesh: sonTileMesh });
                    sonTileMesh.relationCount = 1;
                }
            }
        }

        let self = this;
        let param = {
            tileNum: { x: tileX, y: tileY, z: tileZ }, urlBase: this.baseTileUrl, callback: function (curTileMesh) {
                sonTileArray.forEach(sonTile => {
                    if (sonTile.tileMesh) {
                        sonTile.tileMesh.dispose();
                    }
                    self.tileMap.delete(sonTile.tileIID);
                });
            }
        }
        let tileMesh = new ImageTileMesh(param);
        newTileArray.push({ tileIID: tileNumStr, tileMesh: tileMesh });
    })

     this.removeTileWithZoom(mapLevel); //删除没有被标记到的瓦片
     newTileArray.forEach(element => {
        this.tileMap.set(element.tileIID, element.tileMesh);
        GlobalScene.instance.addTile(element.tileMesh);
    });
}

    private testOutput() {
    // 获取tileMap的所有值（ImageTileMesh对象）
    const tileMeshes = this.tileMap.values();

    // 遍历所有瓦片网格对象
    for (const tileMesh of tileMeshes) {
        // 输出每个元素的relationCount属性
        tileMesh.material.needsUpdate = true;
        console.log(`Tile relationCount: ${tileMesh.relationCount}` + ', mesh visible: ' + tileMesh.visible + ', material visible: ' + tileMesh.material.visible);
    }
}

    private removeTileWithZoom(mapLevel: number) {
    //遍历this.tileMap, 检查每个元素的relationCount属性是否为0, 如果为0, 就删除该元素
    const tilesToRemove = [];
    //收集需要删除的瓦片和重置relationCount
    this.tileMap.forEach((tileMesh, tileNumStr) => {
        let tileNumJson: { row: number, column: number, mapLevel: number } = JSON.parse(tileNumStr);
        if (tileMesh.relationCount == 0) { // || Math.abs(mapLevel - tileNumJson.mapLevel)>1
            tilesToRemove.push({ tileMesh, tileNumStr });
        }
    });

    tilesToRemove.forEach(({ tileMesh, tileNumStr }) => {
        try {
            if (tileMesh) {
                tileMesh.dispose();
            }
            this.tileMap.delete(tileNumStr);
        } catch (error) {
            console.error(`Error disposing tile ${tileNumStr}:`, error);
        }
    });
}

    /**非层级切换时，加载瓦片 */
    public loadTiles(visibleTileStrSet: Set < string >) {
    // 差集：this.lastTileNumStrSet - visibleTileStrSet
    const differenceTileSet = new Set([...this.lastTileNumStrSet].filter(x => !visibleTileStrSet.has(x)));
    differenceTileSet.forEach(tileNumStr => {
        let tileMesh = this.tileMap.get(tileNumStr);
        if (tileMesh) {
            tileMesh.dispose();
        }
        this.tileMap.delete(tileNumStr);
    });

    //加载visibleTileSet集合中的瓦片元素
    visibleTileStrSet.forEach(tileNumStr => {
        let tileNumJson: { row: number, column: number, mapLevel: number } = JSON.parse(tileNumStr);
        let tileX = tileNumJson.column;
        let tileY = tileNumJson.row;
        let tileZ = tileNumJson.mapLevel;

        if (!this.tileMap.has(tileNumStr)) { //瓦片没有被加载
            let param = { tileNum: { x: tileX, y: tileY, z: tileZ }, urlBase: this.baseTileUrl }
            let tileMesh = new ImageTileMesh(param);
            GlobalScene.instance.addTile(tileMesh);
            this.tileMap.set(tileNumStr, tileMesh);
        }
    });

    this.lastTileNumStrSet = visibleTileStrSet;
}

    /* 获取瓦片周边的瓦片 */
    private loadAroundTilesNum(column: number, row: number, mapLevel: number, visibleTileStrSet: Set < String >) {
    let currentTile = { row: row, column: column, mapLevel: mapLevel };
    let leftTile = { row: row - 1, column: column, mapLevel: mapLevel };
    let rightTile = { row: row + 1, column: column, mapLevel: mapLevel };
    let topTile = { row: row, column: column + 1, mapLevel: mapLevel };
    let bottomTile = { row: row, column: column - 1, mapLevel: mapLevel };
    let leftTopTile = { row: row - 1, column: column + 1, mapLevel: mapLevel };
    let leftDownTile = { row: row - 1, column: column - 1, mapLevel: mapLevel };
    let rightTopTile = { row: row + 1, column: column + 1, mapLevel: mapLevel };
    let rightDownTile = { row: row + 1, column: column - 1, mapLevel: mapLevel };

    visibleTileStrSet.add(JSON.stringify(currentTile));

    let visibleTileArray: Array<{ row: number, column: number, mapLevel: number }> = new Array < { row: number, column: number, mapLevel: number } > ();
    let aroundTiles = [leftTile, rightTile, topTile, bottomTile, leftTopTile, leftDownTile, rightTopTile, rightDownTile];
    aroundTiles.forEach(tileNum => {
        //检测瓦片是否在相机视椎体的范围内；检测在可见范围内的瓦片是否被地球遮挡; 如果都检测通过再把isVisible设置为true
        let tileRegion = this.computeTileRegion(tileNum);

        if (!this.checkTileValide(tileNum)) {
            return;
        }
        if (visibleTileStrSet.has(JSON.stringify(tileNum))) {
            return;
        }

        let isInCameraVolumn = CameraUtil.checkVisible(tileRegion.tileBox, this.camera);
        if (isInCameraVolumn) {
            //检测瓦片是否被地球遮挡住了
            if (CameraUtil.checkCover(tileRegion.tileSphere, this.camera)) { //checkCover2
                visibleTileArray.push(tileNum);
            }
        }
    });

    if (visibleTileArray.length > 0) {
        visibleTileArray.forEach(tileNum => {
            visibleTileStrSet.add(JSON.stringify(tileNum));
            this.loadAroundTilesNum(tileNum.column, tileNum.row, tileNum.mapLevel, visibleTileStrSet);
        });
    }
}

    /**计算瓦片的包围体 */
    private computeTileRegion(tileNum: { row: number, column: number, mapLevel: number }) {
    let webCenterX = GeoUtils.getCDevX(tileNum.column, tileNum.mapLevel, 0.5);
    let webCenterY = GeoUtils.getRDevY(tileNum.row, tileNum.mapLevel, 0.5);
    let ecefCenterPoint = GeoUtils.webToEcef(webCenterX, webCenterY);

    let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
    let radius = -Infinity;
    for (let i = 0; i <= 1; i++) {
        for (let j = 0; j <= 1; j++) {
            let webX = GeoUtils.getCDevX(tileNum.column, tileNum.mapLevel, i);
            let webY = GeoUtils.getRDevY(tileNum.row, tileNum.mapLevel, j);
            let ecefPoint = GeoUtils.webToEcef(webX, webY);

            minX = Math.min(minX, ecefPoint.x);
            maxX = Math.max(maxX, ecefPoint.x);
            minY = Math.min(minY, ecefPoint.y);
            maxY = Math.max(maxY, ecefPoint.y);
            minZ = Math.min(minZ, ecefPoint.z);
            maxZ = Math.max(maxZ, ecefPoint.z);

            let distanceToCenter = ecefPoint.distanceTo(ecefCenterPoint);
            radius = Math.max(distanceToCenter, radius);
        }
    }

    let minPoint = new Vector3(minX, minY, minZ);
    let maxPoint = new Vector3(maxX, maxY, maxZ);
    let tileBox: Box3 = new Box3(minPoint, maxPoint);
    let tileSpher: Sphere = new Sphere(ecefCenterPoint, radius);
    return { tileSphere: tileSpher, tileBox: tileBox };
}

    /**检查瓦片是否合法 */
    public checkTileValide(tileNum: { row: number, column: number, mapLevel: number }) {
    let maxTileWidthCount = Math.pow(2, tileNum.mapLevel);

    if (tileNum.column >= maxTileWidthCount) {
        tileNum.column = tileNum.column % maxTileWidthCount;
    }
    if (tileNum.column < 0) {
        tileNum.column = maxTileWidthCount - Math.abs(tileNum.column);
    }

    if (tileNum.row < 0 || tileNum.row >= maxTileWidthCount) {
        return false;
    }

    return true;
}
}